﻿using BeetleX.Buffers;
using BeetleX.Clients;
using System;
using System.Buffers;
using System.Collections.Generic;
using System.Threading;

#nullable disable
namespace BeetleX.XRPC
{
    public class RPCPacket : IDisposable
    {
        private int mRetain;
        private bool mBufferData;
        private Options mOption;
        private int mIsDisposed;

        public RPCPacket() => Status = 200;

        public bool NeedReply { get; set; }

        public int ID { get; set; }

        public string Url { get; set; }

        public Dictionary<string, string> Header { get; set; }

        public short Status { get; set; }

        public int Length { get; internal set; }

        public int Paramters { get; internal set; }

        public object[] Data { get; set; }

        public Type[] ResultType { get; set; }

        public ArraySegment<byte>? PayloadData { get; internal set; }

        public ISession Session { get; internal set; }

        public AsyncTcpClient Client { get; internal set; }

        public int ContentLength { get; internal set; }

        public override string ToString() => this.Url;

        public bool Read(Options rpcOption, PipeStream stream)
        {
            this.mOption = rpcOption;
            if (this.Length == 0 && stream.Length > 4L)
            {
                this.Length = stream.ReadInt32();
                if (this.Length > rpcOption.MessageMaxLength)
                    throw new BXException("The message to long!");
            }
            if (this.Length <= 0 || stream.Length < (long)this.Length)
                return false;
            this.mRetain = (int)(stream.Length - (long)this.Length);
            try
            {
                uint num1 = stream.ReadUInt32();
                this.NeedReply = num1 >> 30 > 0U;
                this.ID = (int)(num1 << 4 >> 4);
                this.Url = stream.ReadShortUTF();
                this.Status = stream.ReadInt16();
                int num2 = stream.ReadByte();
                this.Header = new Dictionary<string, string>();
                if (num2 > 0)
                {
                    for (int index = 0; index < num2; ++index)
                        this.Header[stream.ReadShortUTF()] = stream.ReadShortUTF();
                }
                this.Paramters = stream.ReadByte();
                this.ContentLength = (int)stream.Length - this.mRetain;
                if (this.ContentLength > 0)
                {
                    byte[] numArray = rpcOption.PopBuffer(this.ContentLength);
                    stream.Read(numArray, 0, this.ContentLength);
                    this.PayloadData = new ArraySegment<byte>?(new ArraySegment<byte>(numArray, 0, this.ContentLength));
                }
            }
            catch (Exception ex)
            {
                throw new BXException("Read protocol data error " + ex.Message, ex);
            }
            return true;
        }

        public void Write(Options rpcOption, PipeStream stream)
        {
            this.mOption = rpcOption;
            try
            {
                MemoryBlockCollection memoryBlockCollection = stream.Allocate(4);
                int cacheLength = stream.CacheLength;
                uint id = (uint)this.ID;
                if (this.NeedReply)
                    id |= 1073741824U;
                stream.Write(id);
                stream.WriteShortUTF(this.Url);
                stream.Write(this.Status);
                if (this.Header != null)
                {
                    stream.Write((byte)this.Header.Count);
                    foreach (KeyValuePair<string, string> keyValuePair in this.Header)
                    {
                        stream.WriteShortUTF(keyValuePair.Key);
                        stream.WriteShortUTF(keyValuePair.Value);
                    }
                }
                else
                    stream.Write((byte)0);
                if (this.Data != null)
                {
                    stream.Write((byte)this.Data.Length);
                    for (int index = 0; index < this.Data.Length; ++index)
                        rpcOption.ParameterFormater.Encode(rpcOption, this.Data[index], stream);
                }
                else
                    stream.Write((byte)0);
                memoryBlockCollection.Full(stream.CacheLength - cacheLength);
            }
            catch (Exception ex)
            {
                throw new BXException("Write protocol data error " + ex.Message, ex);
            }
        }

        public void LoadParameters<T, T1, T2, T3, T4>()
        {
            this.LoadParameters(typeof(T), typeof(T1), typeof(T2), typeof(T3), typeof(T4));
        }

        public void LoadParameters<T, T1, T2, T3>()
        {
            this.LoadParameters(typeof(T), typeof(T1), typeof(T2), typeof(T3));
        }

        public void LoadParameters<T, T1, T2>()
        {
            this.LoadParameters(typeof(T), typeof(T1), typeof(T2));
        }

        public void LoadParameters<T, T1>() => this.LoadParameters(typeof(T), typeof(T1));

        public void LoadParameters<T>() => this.LoadParameters(typeof(T));

        public void LoadParameters(params Type[] types)
        {
            Options mOption = this.mOption;
            try
            {
                this.mBufferData = types == null || types.Length == 0;
                if (!this.PayloadData.HasValue)
                    return;
                ArraySegment<byte>? payloadData = this.PayloadData;
                byte[] array = payloadData.Value.Array;
                payloadData = this.PayloadData;
                int startIndex = payloadData.Value.Offset;
                if (this.Status == (short)200)
                {
                    this.Data = new object[this.Paramters];
                    for (int index = 0; index < this.Paramters; ++index)
                    {
                        Type type = types == null || types.Length == 0 ? (Type)null : types[index];
                        int int32 = BitConverter.ToInt32(array, startIndex);
                        int offset = startIndex + 4;
                        this.Data[index] = mOption.ParameterFormater.Decode(mOption, type, new ArraySegment<byte>(array, offset, int32));
                        startIndex = offset + int32;
                    }
                }
                else
                {
                    int int32 = BitConverter.ToInt32(array, startIndex);
                    int offset = startIndex + 4;
                    this.Data = new object[1] { mOption.ParameterFormater.Decode(mOption, typeof(string), new ArraySegment<byte>(array, offset, int32)) };
                }
            }
            finally
            {
                ArraySegment<byte>? nullable = this.PayloadData;
                if (nullable.HasValue)
                {
                    Options options = mOption;
                    nullable = this.PayloadData;
                    byte[] array = nullable.Value.Array;
                    int contentLength = this.ContentLength;
                    options.PushBuffer(array, contentLength);
                    nullable = new ArraySegment<byte>?();
                    this.PayloadData = nullable;
                }
            }
        }

        public void Dispose()
        {
            if (Interlocked.CompareExchange(ref this.mIsDisposed, 1, 0) != 0)
                return;
            if (this.mBufferData && this.Data != null)
            {
                foreach (object obj in this.Data)
                {
                    if (obj is ArraySegment<byte> arraySegment)
                        ArrayPool<byte>.Shared.Return(arraySegment.Array);
                }
            }
            if (!this.PayloadData.HasValue)
                return;
            Options mOption = this.mOption;
            ArraySegment<byte>? nullable = this.PayloadData;
            byte[] array = nullable.Value.Array;
            int contentLength = this.ContentLength;
            mOption.PushBuffer(array, contentLength);
            nullable = new ArraySegment<byte>?();
            this.PayloadData = nullable;
        }

        public void ReplyError(short status, string message)
        {
            this.ReplyPacket(new RPCPacket() { Status = status, Data = new string[1] { message } });
        }

        public void ReplyError(string message)
        {
            this.ReplyPacket(new RPCPacket() { Status = 500, Data = new string[1] { message } });
        }

        public void ReplySuccess()
        {
            this.ReplyPacket(new RPCPacket() { Status = (short)200 });
        }

        public void Reply(params object[] data)
        {
            this.ReplyPacket(new RPCPacket() { Status = 200, Data = data });
        }

        public void ReplyPacket(RPCPacket response)
        {
            response.ID = this.ID;
            if (this.Session != null)
                this.Session.Send(response);
            if (this.Client != null)
                this.Client.Send(response);
        }
    }
}
